from IPython.display import HTML
HTML('''<script>
code_show=true;
function code_toggle() {
if (code_show){
$('div.input').hide();
} else {
$('div.input').show();
}
code_show = !code_show
}
$( document ).ready(code_toggle);
</script>
El código está oculto, para mostrarlo haz click <a href="javascript:code_toggle()">aquí</a>.''')
HTML('''<script>
code_show=true;
function code_toggle() {
if (code_show){
$('div.input').hide();
} else {
$('div.input').show();
}
code_show = !code_show
}
$( document ).ready(code_toggle);
</script>
El código está oculto, para mostrarlo haz click <a href="javascript:code_toggle()">aquí</a>.''')
Indice
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import geopandas as gdp
import descartes
import mplleaflet
import folium
from shapely.geometry import Point
%matplotlib inline
sns.set_style("whitegrid")
# Reducción de márgenes
from IPython.core.display import display, HTML
display(HTML("<style>.container{ width:78% }</style>"))
df = pd.read_csv("./data/airbnb_NYC_2019.csv", sep=",")
df.head()
Descripción de columnas:
id: listing IDname: name of the listinghost_id: host IDhost_name: name of the hostneighbourhood_group: locationneighbourhood: arealatitude: latitude coordinateslongitude: longitude coordinatesroom_type: listing space typeprice: price in dollarsminimum_nights: amount of nights minimumnumber_of_reviews: number of reviewslast_review: latest reviewreviews_per_month: number of reviews per monthcalculated_host_listings_count: amount of listing per host // NUMERO DE APARTAMENTOS QUE TIENE EL ANFITRIONavailability_365: number of days when listing is available for bookingSi más adelante quisieramos crear modelos de ML, los dos primeros pasos que se van a llevar a cabo son cruciales ya que las métricas podrían verse muy afectadas en función de la imputación que se decida llevar a cabo.
df.isnull().any()
df.isnull().sum()/df.shape[0]
for key in df.columns:
valores_nulos = df[key].isnull().sum()
proporcion = (valores_nulos / len(df[key]))*100
print("La variable llamada " + str(key) + " tiene " + str(valores_nulos) + " valores nulos, en concreto forman un " + str(proporcion) + "%" )
prop_notna = df.notna().sum()/len(df)
plt.figure(figsize=(10,6))
bar_width = 0.5
positions = np.arange(len(prop_notna))
plt.bar(positions, prop_notna, bar_width, alpha=0.25)
plt.title('Proporción de lecturas registradas.', fontsize=5, fontweight='bold');
plt.xlabel('Estación', fontsize=7, fontweight='bold', labelpad=100);
xticks = positions
plt.xticks(xticks, prop_notna.index, rotation = 70, fontsize=15);
plt.tick_params(left=False, labelleft=False)
for bar_id in positions:
plt.text(bar_id, prop_notna.values[bar_id]+0.025,
'{:.1f}%'.format(prop_notna.values[bar_id]*100),
horizontalalignment='center', verticalalignment='center',
fontsize=11, color='#004D7F',fontweight='bold');
Se detecta que existen valores faltantes en las variables name, last_review y reviews_per_month y el porcentaje de los mismos. Se decide imputar los valores faltantes con un 0 y conservar dichas observaciones ya que en principio la información que aportan el resto de variables es relevante para el análisis. La descripción (name) no es especialmente relevante, probablemente no lo rellenarián por ahorro de tiempo y en cuanto a las revisiones, es lógico pensar que si falta ese dato se asume que ha sido 0.
df1 = df.fillna(0)
df1.head()
df1.info()
Se observa como el número de columnas total es 15 y el número de observaciones es 48.895. Además, se obsera el tipo de valor asignado a cada columna. Es destacable, que el número de reviews_per_month es float, probablemente es debido a que realizaron una media entre todas las reviews reallizadas en un año, por meses.
Para analizar las variables numericas se van a utilizar gráficos de caja para intentar detectar outliers de manera visual y aproximada.
numericas = df1[["price", "minimum_nights", "number_of_reviews", "reviews_per_month", "calculated_host_listings_count", "availability_365"]]
numericas.head()
numericas_sin_p = df1[["minimum_nights", "number_of_reviews", "reviews_per_month", "calculated_host_listings_count", "availability_365"]]
with sns.axes_style(style='ticks'):
g = sns.catplot( data= numericas_sin_p, height=10.5, aspect=1, kind="box", palette= "BuPu")
with sns.axes_style(style='ticks'):
g = sns.catplot(data=df1[["price"]], kind="violin", height=10.5, aspect=1, palette= "BuPu")
Dado que la variable price posee gran dispersión se decide visualizarla de manera aislada, con estos gráficos se observan los outliers pero no es suficiente para tomar una decisión definitiva
df1.describe()
Teniendo en cuenta el significado de cada variable, se observa la media con respecto a los valores máximos y mínimos que toma.
Se observan las siguientes cuestiones relevantes:
dataset_filtered = df1.loc[(df1['price'] > 0) &
(df['minimum_nights'] <= 365) &
(df['calculated_host_listings_count'] <= 200) &
(df['availability_365'] >= 1)]
dataset_filtered.describe()
Se observa como se han reducido 1700 observaciones aproximadamente, consideradas como outliers
Se calcula el host_id (anfitrion) cuantos id tiene a su nombre, es decir cuantos anuncios de casas tiene publicados y se comprueba si es correcta la variable calculated_host_listings_count
host=df1.groupby("host_id").count().loc[:,["id"]].groupby("host_id").count()
final= pd.merge(df1, host, on='host_id').loc[:,["host_id","id_y","calculated_host_listings_count"]].groupby("host_id").count()
final
final.loc[final.id_y!=final.calculated_host_listings_count,["host_id","id_y","calculated_host_listtings_count"]]
Con el código anterior se observa como efectivamente el número de viviendas que posee un anfitrión coincide con la columna calculated_host_listings_count
matrix = np.triu(dataset_filtered.corr())
sns.heatmap(dataset_filtered.corr(), annot=True, mask=matrix, linewidths=.2, fmt=".1", cmap="BuPu")
A través de este gráfico se puede observar la correlación existente entre variables, las que más correlación poseen entre sí son host_id e id y por otro lado reviews_per_month y number_of_reviews, es lógico que esten muy relacionadas entre sí ya que la información que arrojan es muy similar
sns.set(style="ticks", color_codes=True, palette= "pastel")
g = sns.pairplot(df1)
A través de este gráfico se pueden observar tanto histogramas como gráficos de dispersión y observar las relaciones existentes entre variables. Por ejemplo se puede observar como la variable latitud posee una distribucción de campana de gauss o que los valores de id son totalmente dispersos
tips = sns.load_dataset("tips")
ax = sns.scatterplot(x= dataset_filtered['neighbourhood_group'], y=dataset_filtered['price'], data=tips, palette= "pastel")
A través de este gráfico se puede observar la distribución de precios en función de los barrios, siendo Manhattan el más caro y Bronx el más barato con sus respectivos outliers
tips = sns.load_dataset("tips")
ax = sns.scatterplot(x= dataset_filtered['price'], y=dataset_filtered['number_of_reviews'], data=tips, palette= "pastel")
Como es lógico las viviendas que poseen más reviews son las mas baratas y por consiguiente son las más visitadas
Enumeración de todos los neighbourhood existentes en el dataset
dataset_filtered['neighbourhood'].unique()
neigh = pd.DataFrame(dataset_filtered.groupby('neighbourhood')['price'].mean())
neigh.describe()
dataset_filtered.groupby('neighbourhood')['price'].aggregate(['min', max, np.median, np.mean,])
df1['neighbourhood_group'].unique()
dataset_filtered.groupby('neighbourhood_group')['price'].aggregate(['min', max, np.median, np.mean])
Se observa como de media el neighbourhood_group mas caro es Manhattan y el más barato Bronx.
Es importante tener en cuenta, que en estos casos es más representativo de la realidad la mediana que la media, ya la media esta está muy influenciada por los valores extremos.
df3 = pd.DataFrame(dataset_filtered.groupby('host_id')['number_of_reviews'].count()).sort_values('number_of_reviews', ascending = False)
df3[:10]
df4 = pd.DataFrame(dataset_filtered.groupby(["neighbourhood_group","neighbourhood"])['number_of_reviews'].count()).sort_values('number_of_reviews', ascending = False )
df4[:10]
Se observa como los barrios representados de manera individual en función del número de reviews que poseen, del top 10, 6 pertenecen al grupo de Manhattan
df5 = pd.DataFrame(dataset_filtered.groupby('neighbourhood_group') ['number_of_reviews'].count()).sort_values('number_of_reviews', ascending = False)
df5[:10]
with sns.axes_style('white'):
g = sns.catplot(x="neighbourhood_group", y="id" , data=dataset_filtered, aspect=1,
kind="bar", palette= "BuPu")
g.set_xticklabels(step=1)
with sns.axes_style('white'):
g = sns.catplot(x="neighbourhood_group", y="id" ,hue="room_type", data=dataset_filtered, aspect=1,
kind="bar", palette= "BuPu")
g.set_xticklabels(step=1)
Se observa como el grupo de barrios que más viviendas publicadas en airbnb posee es Bronx
with sns.axes_style('white'):
g = sns.catplot(x="room_type", y="minimum_nights" ,hue="neighbourhood_group", data=dataset_filtered, aspect=1,
kind="bar", palette= "BuPu")
g.set_xticklabels(step=1)
with sns.axes_style('white'):
g = sns.catplot(x="neighbourhood_group", y="price" ,hue="room_type", data=dataset_filtered, aspect=1,
kind="bar", palette= "BuPu")
g.set_xticklabels(step=1)
n_rev2 = dataset_filtered['last_review'].value_counts().to_frame().iloc[1:1765].sort_index()
n_rev1 = n_rev2.loc[n_rev2.index >= '2018-06-08']
n_rev1['last_review'].plot(figsize = (10,8))
# Ampliación de márgenes
from IPython.core.display import display, HTML
display(HTML("<style>.container{ width:98% }</style>"))
Esta libreria permite visualizar todos los ids recogidos en el dataset, que en concreto son 30.781 observaciones y pintarlas todas sobre el mapa de NY. Sin embargo, como defecto posee que es mucho menos visual que las siguientes
mapa=gdp.read_file("geo_export_45c5eca0-1f56-4ba9-94f2-25f768babb92.shp")
fig,ax=plt.subplots(figsize=(20,20))
mapa.plot(ax=ax,alpha=0.5, edgecolors="gray")
geometry = [Point(xy) for xy in zip(dataset_filtered['longitude'], dataset_filtered['latitude'])]
gdf = gdp.GeoDataFrame(dataset_filtered, crs="crs", geometry=geometry)
gdf.plot(marker='o', color='steelblue', markersize=0.2, figsize=(20,20))
fig,ax=plt.subplots(figsize=(20,20))
gdf.plot(ax=ax,marker='.', color='steelblue',markersize=20)
mapa.plot(ax=ax,alpha=0.1, edgecolors="k")
Esta librería nos permite visualizar de manera mucho más exacta cual es la localización del anuncio de airbnb, el problema que tiene es que apartir de unas 1000 observaciones es incapaz de pintarlas y no permite visualizar todas las viviendas. Para solucionar este inconveniente se podría fraccionar el dataset en grupos de barrios y visualizarlos de manera independiente o cualquier otro tipo de segmentación para poder visualizar todas las que interesen. En este ejemplo se visualizan las 1000 primeras observaciones ya que sus posibillidades son muy reducidas
mapa = dataset_filtered[:1000]
mapa
mapa.to_csv(index=False, path_or_buf = 'mapa.csv')
from IPython.display import IFrame
fig = plt.figure(figsize=(8,8))
plt.scatter(mapa['longitude'], mapa['latitude'], alpha=0.5, s=30, c='mediumpurple')
mplleaflet.display(fig=fig)
Para visualizarlo de manera más exhaustiva a través de una ventana emergente
mplleaflet.show(fig=fig)
Otra alternativa, mucho más completa, es folium. Esta librería utiliza recursos de otra, denominada leaflet que está escrita en Javascript y es una de las de referencia en visualización de datos geospaciales A continuación se mostrará la ubicación de una vivienda en concreto, utilizando una marca.
m = folium.Map(
location=[39.3014, -72.7390],
zoom_start=8,
tiles='Stamen Terrain'
)
folium.Marker(
location=[40.71788, -73.98975],
popup=folium.Popup(max_width=450).add_child(
folium.Vega(mapa, width=450, height=250))
).add_to(m)
m
Se represetan los ids en el mapa de NY a través de 3 diferentes librerias ya que cada una aporta funcionalidades diferentes.